import requests
import pandas as pd
import numpy as np
import logging
import time
import random
from bs4 import BeautifulSoup
from tqdm import tqdm

# 配置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

class SinaFinanceDividendSpider:
    """
    新浪财经历史分红数据爬虫
    用于爬取新浪财经网站的历史分红数据
    """
    
    def __init__(self):
        """
        初始化爬虫
        """
        # 新浪财经分红数据的URL模板
        self.base_url = "https://vip.stock.finance.sina.com.cn/q/go.php/vFinanceAnalyze/kind/dzgg/index.phtml"
        self.headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
            'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
            'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
            'Connection': 'keep-alive',
        }
        self.data = []
    
    def fetch_page(self, page):
        """
        获取指定页码的数据
        
        Args:
            page: 页码
            
        Returns:
            dict or None: 包含当页数据的字典，失败返回None
        """
        try:
            # 构建请求参数
            params = {
                'p': page,
                'num': 40,  # 每页显示40条数据
            }
            
            # 发送请求
            response = requests.get(
                self.base_url,
                headers=self.headers,
                params=params,
                timeout=30
            )
            
            # 检查响应状态
            if response.status_code != 200:
                logger.error(f"获取第{page}页失败，状态码: {response.status_code}")
                return None
            
            # 解析HTML
            soup = BeautifulSoup(response.text, 'html.parser')
            
            # 找到表格
            table = soup.find('table', class_='data')
            if not table:
                logger.warning(f"第{page}页没有找到数据表格")
                return None
            
            # 提取数据行
            rows = table.find_all('tr')[1:]  # 跳过表头
            page_data = []
            
            for row in rows:
                cols = row.find_all('td')
                if len(cols) >= 12:  # 确保有足够的列
                    item = {
                        '代码': cols[0].text.strip(),
                        '名称': cols[1].text.strip(),
                        '分红方案': cols[2].text.strip(),
                        '股权登记日': cols[3].text.strip(),
                        '除权除息日': cols[4].text.strip(),
                        '派息日': cols[5].text.strip(),
                        '送股比例': cols[6].text.strip(),
                        '转增比例': cols[7].text.strip(),
                        '派息比例': cols[8].text.strip(),
                        '详细': cols[9].text.strip(),
                        '公告日期': cols[10].text.strip(),
                        '预案公告日': cols[11].text.strip(),
                    }
                    page_data.append(item)
            
            logger.info(f"成功获取第{page}页数据，共{len(page_data)}条")
            return page_data
            
        except Exception as e:
            logger.error(f"获取第{page}页数据时出错: {str(e)}")
            return None
    
    def crawl_all_pages(self, start_page=1, end_page=100):
        """
        爬取所有页面的数据
        
        Args:
            start_page: 起始页码
            end_page: 结束页码
        """
        logger.info(f"开始爬取第{start_page}至{end_page}页的数据")
        
        for page in tqdm(range(start_page, end_page + 1)):
            # 爬取当前页
            page_data = self.fetch_page(page)
            
            if page_data:
                self.data.extend(page_data)
            
            # 随机延迟，避免被反爬
            delay = random.uniform(1.0, 3.0)
            time.sleep(delay)
            
            # 每10页休息更长时间
            if page % 10 == 0:
                long_delay = random.uniform(5.0, 10.0)
                logger.info(f"已爬取{page}页，休息{long_delay:.2f}秒")
                time.sleep(long_delay)
        
        logger.info(f"爬取完成，共获取{len(self.data)}条数据")
    
    def save_to_excel(self, file_path="新浪财经历史分红数据.xlsx"):
        """
        保存数据到Excel文件
        
        Args:
            file_path: 保存路径
            
        Returns:
            bool: 是否成功保存
        """
        if not self.data:
            logger.warning("没有数据可保存")
            return False
        
        try:
            # 转换为DataFrame
            df = pd.DataFrame(self.data)
            
            # 保存到Excel
            df.to_excel(file_path, index=False, engine='openpyxl')
            
            logger.info(f"数据已成功保存到 {file_path}，共{len(df)}条记录")
            return True
            
        except Exception as e:
            logger.error(f"保存数据时出错: {str(e)}")
            return False
    
    def run(self, start_page=1, end_page=100, output_file="新浪财经历史分红数据.xlsx"):
        """
        运行完整的爬取流程
        
        Args:
            start_page: 起始页码
            end_page: 结束页码
            output_file: 输出文件路径
            
        Returns:
            bool: 是否成功完成
        """
        try:
            # 爬取数据
            self.crawl_all_pages(start_page, end_page)
            
            # 保存数据
            return self.save_to_excel(output_file)
            
        except Exception as e:
            logger.error(f"运行爬虫时出错: {str(e)}")
            return False
    
    def crawl_with_retry(self, start_page=1, end_page=100, max_retries=3):
        """
        带重试机制的爬取方法
        
        Args:
            start_page: 起始页码
            end_page: 结束页码
            max_retries: 最大重试次数
        """
        failed_pages = []
        
        # 第一次爬取所有页面
        for page in tqdm(range(start_page, end_page + 1)):
            page_data = None
            retries = 0
            
            while retries < max_retries and page_data is None:
                page_data = self.fetch_page(page)
                retries += 1
                
                if page_data is None and retries < max_retries:
                    logger.info(f"重试获取第{page}页，剩余重试次数: {max_retries - retries}")
                    time.sleep(random.uniform(3.0, 5.0))
            
            if page_data:
                self.data.extend(page_data)
            else:
                failed_pages.append(page)
                logger.warning(f"第{page}页爬取失败")
            
            # 随机延迟
            time.sleep(random.uniform(1.0, 3.0))
        
        # 重试失败的页面
        if failed_pages:
            logger.info(f"开始重试失败的{len(failed_pages)}个页面")
            
            for page in failed_pages:
                page_data = self.fetch_page(page)
                if page_data:
                    self.data.extend(page_data)
                    logger.info(f"成功重试第{page}页")
                time.sleep(random.uniform(2.0, 4.0))
        
        logger.info(f"爬取完成，共获取{len(self.data)}条数据")

def main():
    """
    主函数，启动爬虫
    """
    logger.info("启动新浪财经历史分红数据爬虫")
    
    # 创建爬虫实例
    spider = SinaFinanceDividendSpider()
    
    try:
        # 爬取前100页数据
        success = spider.run(start_page=1, end_page=100)
        
        if success:
            logger.info("爬虫任务完成！")
        else:
            logger.error("爬虫任务失败！")
            
    except Exception as e:
        logger.error(f"爬虫运行过程中发生错误: {str(e)}")

if __name__ == "__main__":
    main()